<?php
/**
 * Auth认证类
 * Created by RisingSun
 * User: MarkSpace
 * Create on 2023/3/26 15:16
 */

namespace common;

use Mscore\Core\Db;
use Mscore\Core\Util\Util;
use Mscore\Core\Redis;
use Mscore\Core\Lang;

class Auth
{
   
    private $account='user_account';
    private $online='user_online';
    private $log='user_log';
    private $request=null;
    private $loginset=[];

    /**
     * 初始化类自动载入
     * @param string $lang
     * @return false|object|null
     */
    public function __construct($request='')
    {
        $this->header =get_all_headers();
        $this->header['language']=isset($this->header['language'])&&!empty($this->header['language'])?$this->header['language']:config('langs.default');
        Lang::init($this->header['language']);
        $this->request=$request;
        $this->loginset=config('loginset');
    }

    /**
     * 登录
     * @param username password type表名 lang 语言
     * @return Response
     * @throws BusinessException
     */
    public function userAuth($username='',$password='',$ip='')
    {
        if (empty($username)) {
            $this->log([],$username,'用户为空');
            return Lang::get('Accountempty');
        }
        //黑白名单
        if (intval($this->loginset['iptype'])>0) {
            if(!empty($this->loginset['ips'])){
               $ipcontent=explode(',',str_replace(' ','',$this->loginset['ips']));
            }
            $ip=!empty($ip)?$ip:realIp();//獲取ip
            foreach ($ipcontent as $key => $value) {
                switch ($this->loginset['iptype']) {
                    case '1'://黑名單
                        if(matchip($ip, $value)){
                            $this->log([],$username,'非法IP登录',$ip);
                            return Lang::get('Logfail');
                        }              
                        break;
                    case '2': //白名單
                        if(!matchip($ip, $value)){
                            $this->log([],$username,'非法IP登录',$ip);
                            return Lang::get('Logfail'); 
                        }                
                        break;
                    default: //不控制
                        break;
                } 
            }
        }
        if(intval($this->loginset['loginlimit'])>0){
            $mitinfo=$this->checkLoginLimit($username, intval($this->loginset['loginlimit']), intval($this->loginset['limittime']));
            if($mitinfo['code']!=200){
                return $mitinfo;
            }
        }
        $result =  Db::name($this->account)
                   ->field(' aid,pid,username,password,realname,mobile,status,header_url,grade,roletype,ranges ')
                   ->where('(username = "' . $username . '" or mobile = "' . $username . '" or email = "' . $username . '")')
                   ->find();
        $result=$result?$result:[];
        if (empty($result) || !Util::passwordVerify($password, $result['password'])) {
            $this->log($result,$username,'用户错误、用户不存在或密码错误、密码不存在',$ip);
            return Lang::get('Logfail');
        }
        //是否锁定用户
        if ($result['status'] != 1) {
            $this->log($result,$username,'用户被锁定',$ip);
            return Lang::get('Accountblocked');
        }
        if(intval($this->loginset['loginlimit'])){ $this->removeLoginLimit($username); }//成功删除错误验证
        $tkey = getRandStr(8);
        $token=Util::md5Str($result['aid'].time());
        $num = Db::name($this->online)->where('aid',$result['aid'])->count('aid');
        $this->log($result,$username,'登录成功',$ip);
        if($num<intval($this->loginset['multicli'])){
            Db::name($this->online)->insert(
                [
                    'ip' => !empty($ip)?$ip:realIp(), 
                    'time' =>date('Y-m-d H:i:s',time()), 
                    'token'=>Util::md5Str($token.md5($tkey).md5(config('encryption.token_salt'))),
                    'username'=>$result['username'],
                    'realname'=>$result['realname'],
                    'mobile'=>$result['mobile'],
                    'aid'=>$result['aid'],
                    'expire_time'=>time()+Util::getEn_Method('jwtkey')['expirationTime'],
                    'header_url'=>$result['header_url'],
                    'equipment'=>equipment($this->request),
                    'grade'=>$result['grade'],
                    'roletype'=>$result['roletype'],
                    'ranges'=>$result['ranges'],
                    'pid'=>$result['pid'],
                    'tkey'=>$tkey
                ]
            );
        }else{
            $user = Db::name($this->online)->field('id')->where('aid',$result['aid'])->limit(0,1)->order('time ASC')->find();
            Db::name($this->online)->where('id', $user['id'])->update(
                [
                    'ip' => !empty($ip)?$ip:realIp(),
                    'time' =>date('Y-m-d H:i:s',time()), 
                    'token'=>Util::md5Str($token.md5($tkey).md5(config('encryption.token_salt'))),
                    'username'=>$result['username'],
                    'realname'=>$result['realname'],
                    'mobile'=>$result['mobile'],
                    'aid'=>$result['aid'],
                    'expire_time'=>time()+Util::getEn_Method('jwtkey')['expirationTime'],
                    'header_url'=>$result['header_url'],
                    'equipment'=>equipment($this->request),
                    'grade'=>$result['grade'],
                    'roletype'=>$result['roletype'],
                    'ranges'=>$result['ranges'],
                    'pid'=>$result['pid'],
                    'tkey'=>$tkey
                ]
             );
        }
        $sub=['tkey'=>$tkey,'token'=>$token];
        $token = Util::getJwtToken($sub);
        unset($result['password'],$result['aid']);
        $success = Lang::get('Logsuccessful');
        $success['data'] = ['token' => $token];
        return $success;
    }

    /**
     * 验证
     * @param token
     * @return Response
     * @throws BusinessException
     */
    public function isAuth($token)
    {
        if (empty($token)) {
            return Lang::get('Logfail');
        }
        return $this->checktoken($token);        
    }

    /**
     * 获取token
     * @param token
     * @return Response
     * @throws BusinessException
     */
    public function getToken($token='')
    {    
        if (empty($token)) {
            return Lang::get('Logfail');
        }
        $user=$this->checktoken($token);
        if(isset($user['code'])){
            return false && $user;
        }
        $tkey = getRandStr(8);
        $token=Util::md5Str($user['aid'].time());
        $user = Db::name($this->online)->where('aid',$user['aid'])->where('equipment',equipment($this->request))->find();
        if($user){
            Db::name($this->online)->where('aid', $user['aid'])
             ->update(
                [ 
                    'time' =>date('Y-m-d H:i:s',time()), 
                    'token'=>Util::md5Str($token.md5($tkey).md5(config('encryption.token_salt'))),
                    'expire_time'=>time()+Util::getEn_Method('jwtkey')['expirationTime'],
                    'equipment'=>equipment($this->request),
                    'tkey'=>$tkey
                ]
             );
             $this->log($user,$user['username'],'token更新成功',$ip);
        }else{
            $this->log($user,'','token更新失败',$ip);
            return Lang::get('Logfail');
        }
        $sub=['tkey'=>$tkey,'token'=>$token];
        $token = Util::getJwtToken($sub);
        return $token;        
    }

    /**
     * 判断解析token
     * @param string $code
     */
    protected function checktoken($token)
    {
        $jwtinfo['sub']='';
        $jwtinfo=Util::verifyJwtToken($token);
        $user=[];
        if(isset($jwtinfo['code'])){
            return Lang::get('Logfail');
        }
        $user = Db::name($this->online)->where('tkey',$jwtinfo['sub']['tkey'])->find();
        //不存在返回
        if(empty($user)){
            return Lang::get('Logfail');
        }
        switch ($jwtinfo) {
            case isset($jwtinfo['exp'])&&$jwtinfo['exp']>time()-1000:
                switch (true) {
                    case Util::md5Str($jwtinfo['sub']['token'].md5($jwtinfo['sub']['tkey']).md5(config('encryption.token_salt')))==$user['token']&&$user['expire_time']>time():
                        unset($user['token'],$user['tkey'],$user['expire_time']);
                        $success = Lang::get('Logsuccessful');
                        $success['data'] = ['info'=>$user,'token'=>''];
                        return $success;
                        break;
                    default:
                        return Lang::get('Logfail');
                        break;
                }
                break;
            case $jwtinfo['exp']>time()&&($jwtinfo['exp']-1000)<time():
                $token=$this->getToken($token);
                switch (true) {
                    case !empty($token):
                        setCert(['token'=>$token]);//设置凭证
                        $success = Lang::get('Logsuccessful');
                        $success['data'] = ['info'=>$user,'token'=>$token];
                        return $success;
                        break;
                    default:
                        return Lang::get('Logfail');
                        break;
                }
                break;
            default:
                return Lang::get('Logfail');
                break;
        }
    }

    /**
     * 退出
     * @param token
     * @return Response
     * @throws BusinessException
     */
    public function logout($token='',$ip='')
    {
        switch (true) {
            case !empty($token):
                $jwtinfo=[];
                $jwtinfo=Util::verifyJwtToken($token);
                if(isset($jwtinfo['sub'])){
                    $user = Db::name($this->online)->where('tkey',$jwtinfo['sub']['tkey'])->where('equipment',equipment($this->request))->find();
                    Db::name($this->online)->where('tkey',$jwtinfo['sub']['tkey'])->delete();
                    $this->log($user,$user['username'],'退出成功',$ip);
                }else{
                    $this->log([],'未知','token已过期，退出成功',$ip);
                }
                return Lang::get('Logoutsuccessful');
                break;
            default:
                $this->log([],'','退出数据异常',$ip);
                return Lang::get('Logoutsuccessful');
                break;
        }
    }

    /**
     * 检查登录频率限制
     * @param $username
     * @return void
     * @throws BusinessException
     */
    protected function checkLoginLimit(string $username, int $count=5, int $limittime=5)
    {
        $limit_log_path = config('app.runtime_path') . '/login';
        if (!is_dir($limit_log_path)) {
            mkdir($limit_log_path, 0777, true);
        }
        $limit_file = $limit_log_path . '/' . md5($username) . '.limit';
        $time = date('YmdHi');
        $limit_info = [];
        if (is_file($limit_file)) {
            $json_str = file_get_contents($limit_file);
            $limit_info = json_decode($json_str, true);
        }
        if (!isset($limit_info) || !isset($limit_info['time']) || (intval($limit_info['time']) + $limittime) < $time) {
            $limit_info = [
                'username' => $username,
                'count' => 0,
                'time' => $time
            ];
        }
        $limit_info['count']++;
        file_put_contents($limit_file, json_encode($limit_info));
        if ($limit_info['count'] >= $count) {
            $info=Lang::get('Loginlimit');
            $info['msg']=str_replace("{count}", $count, $info['msg']);
            $info['msg']=str_replace("{time}", $limittime, $info['msg']);
            return $info;
        }
        return ['code'=>200];
    }

    /**
     * 解除登录频率限制
     * @param $username
     * @return void
     */
    protected function removeLoginLimit(string $username)
    {
        $limit_log_path = config('app.runtime_path') . '/login';
        $limit_file = $limit_log_path . '/' . md5($username) . '.limit';
        if (is_file($limit_file)) {
            unlink($limit_file);
        }
    }

    /**
     * 记录日志
     * @param username password type表名 lang 语言
     * @return Response
     * @throws BusinessException
     */
    protected function log(array $result=[], string $username='', string $msg='未知操作', string $ip='')
    {
        Db::name($this->log)->insert(
                [
                    'aid'=>isset($result['aid'])?$result['aid']:0,
                    'pid'=>isset($result['pid'])?$result['pid']:0,
                    'username'=>$username,
                    'log_time' =>date('Y-m-d H:i:s',time()), 
                    'log_info'=>$msg,
                    'ip_address' => !empty($ip)?$ip:realIp(),
                    'roletype'=>isset($result['roletype'])?$result['roletype']:0,
                    'equipment'=>equipment($this->request)
                ]
        );        
    }

}